package com.heima.es.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.es.service.EsService;
import com.heima.model.common.anno.EsId;
import com.heima.model.common.dtos.PageResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class EsServiceImpl<T> implements EsService<T> {
    @Autowired
    RestHighLevelClient client;

    @Override
    public void createIndex(String source, String indexName) {
        try {
            // 1.创建Request对象
            CreateIndexRequest request = new CreateIndexRequest(indexName);
            // 2.准备参数，settings和mappings
            request.source(source, XContentType.JSON);
            // 3.发出请求
            CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
            // 4.判断
            if (!response.isAcknowledged()) {
                //创建索引库失败
                throw new RuntimeException("索引库创建失败");
            }
        } catch (IOException e) {
            throw new RuntimeException("索引库创建失败");
        }


    }

    /**
     * 判断索引库是否存在
     * @param indexName 索引库名
     * @return
     */
    @Override
    public boolean existIndex(String indexName) {
        try {
            // 1.创建Request对象
            GetIndexRequest request = new GetIndexRequest(indexName);
            //发出请求
            return  client.indices().exists(request,RequestOptions.DEFAULT);
        }catch (Exception e){
            throw new RuntimeException("索引库创建失败！", e);
        }
    }

    /**
     * 删除索引库
     * @param indexName 索引库名
     */
    @Override
    public void deleteIndex(String indexName) {

        try {
            //创建request对象
            DeleteIndexRequest request = new DeleteIndexRequest(indexName);
            //发出删除请求
            client.indices().delete(request,RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 新增文档
     * @param t
     * @param indexName
     */
    @Override
    public void save(T t, String indexName) {
        try {
            //创建request对象
            IndexRequest request = new IndexRequest(indexName);
            //准备参数 id和文档数据
            request.id(getId(t));
            request.source(JSON.toJSONString(t), XContentType.JSON);
            //发送请求
            client.index(request,RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("新增文档失败！", e);
        }
    }





    /**
     * 文档批量新增
     * @param list
     * @param indexName
     */
    @Override
    public void saveBatch(List<T> list, String indexName) {
        try {
            BulkRequest request = new BulkRequest(indexName);
            //准备参数
            for (T t : list) {
                request.add(new IndexRequest()
                        .id(getId(t))
                        .source(JSON.toJSONString(t),XContentType.JSON));
            }
            //发送请求
            client.bulk(request,RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("批量新增失败！", e);
        }
    }

    /**
     * 通过id删除数据
     * @param id
     * @param indexName
     */
    @Override
    public void deleteById(String id, String indexName) {

        try {
            //创建删除请求对象
            DeleteRequest request = new DeleteRequest(indexName,id);
            //发送删除请求
            client.delete(request,RequestOptions.DEFAULT);
        }catch (IOException e){
            throw new RuntimeException("删除文档失败！", e);
        }
    }

    /**
     * 通过id得到数据
     * @param id
     * @param tClass
     * @param indexName
     * @return
     */
    @Override
    public T getById(String id, Class<T> tClass, String indexName) {
        try {
            //创建请求
            GetRequest getRequest = new GetRequest(indexName);
            //发送请求
            GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
            //解析结果
            String result = getResponse.getSourceAsString();
            //反序列化
            return JSON.parseObject(result,tClass);
        }catch (IOException e){
            throw new RuntimeException("查询文档失败！", e);
        }
    }

    /**
     * 查询并返回结果，并且带高亮、分页、排序
     * @param sourceBuilder 条件
     * @param tClass
     * @param indexName
     * @return
     */
    @Override
    public PageResponseResult search(SearchSourceBuilder sourceBuilder, Class<T> tClass, String indexName) {

        try {
            // 封装分页结果
            PageResponseResult result = new PageResponseResult();
            int from = sourceBuilder.from();
            int size = sourceBuilder.size();
            result.setCurrentPage(from/size+1);
            result.setSize(size);
            // 1.准备request
            SearchRequest searchRequest = new SearchRequest(indexName);
            // 2.准备参数
            searchRequest.source(sourceBuilder);
            // 3.发出请求
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            // 4.解析结果
            SearchHits hits = response.getHits();
            // 4.1.获取总条数
            long total = hits.getTotalHits().value;
            // 4.2.获取数据
            SearchHit[] searchHits = hits.getHits();
            List<T> list = new ArrayList<>(searchHits.length);
            // 4.3.循环处理
            for (SearchHit hit : searchHits) {
                // 获取source
                String json = hit.getSourceAsString();
                // 反序列化
                T t = JSON.parseObject(json, tClass);
                //将对象添加进集合
                list.add(t);
                // 高亮处理
                handleHighlight(t,hit);
            }
            result.setTotal(total);
            result.setData(list);
            return result;
        } catch (Exception e) {
            throw new RuntimeException("查询文档失败！", e);
        }
    }

    /**
     * 设置高亮显示
     * @param t
     * @param hit
     */
    private void handleHighlight(T t, SearchHit hit) throws InvocationTargetException, IllegalAccessException {
        Map<String, HighlightField> fields = hit.getHighlightFields();
        //遍历map集合
        for (HighlightField highlightField : fields.values()) {
            // 获取高亮字段名称
            String name = highlightField.getName();
            // 获取高亮结果
            String highlightValue = StringUtils.join(highlightField.getFragments());
            // 获取字节码
            BeanUtils.setProperty(t,name,highlightValue);
        }
    }


    private String getId(T t) {
        try {
            Class<?> tClass = t.getClass();
            Field[] declaredFields = tClass.getDeclaredFields();
            Object id = null;
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                if (declaredField.isAnnotationPresent(EsId.class)) {
                    id = declaredField.get(t);
                }
            }
            if (id == null) {
                log.error("es工具类出错， 未发现标记@EsId注解 ");
                throw new RuntimeException("实体类中必须包含@TableId注解！");
            }
            return id.toString();
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}